library ArmorUtils requires Logarithm, optional IntuitiveDamageSystem
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This is used to get the exact armor of a given unit. It deals damage to the
//* unit in order to determine armor, meaning that damage detection systems will
//* detect it. If your map has the Intuitive Damage Detection System (IDDS) in
//* it, it will use that system's internal ignored damage type for the check.
//* Using that removes the need to disable/enable any damage detection triggers
//* to avoid infinite loops.
//*
//* The attacktype constant, ATTACK_TYPE_USED, is by default set to use
//* ATTACK_TYPE_CHAOS. Chaos is used because it deals 100% to all types, so if
//* you change it in your map, make sure you update the constant to some type
//* with 100% damage to all armor types.
//*
//* This system can also be used as a means to detect if a unit is invulnerable
//* or not. If GetUnitArmor returns ARMOR_INVULNERABLE, then the unit is
//* invulnerable. The value for it is entirely random and will never be the
//* actual armor value for a unit, so there should be no conflicts.
//*
//* Damage reduction in WC3 due to negative armor is capped at -71%, which has
//* an equivalent armor value of -20. If you have a unit with less than -20
//* armor, this system will always return exactly -20.
//*
//* Default WC3 gameplay constants have the 'Armor Damage Reduction Multiplier'
//* set to 0.06. If you change this constant for your map, be sure to adjust the
//* ARMOR_REDUCTION_MULTIPLIER constant in this script to match.
//*
//* Example Usage:
//*     local real armor = GetUnitArmor(MyUnit)
//*
//* Two other functions are included in this library. They are used for
//* calculating a unit's base damage knowing armor-factoring damage and a unit's
//* armor-factoring damage knowing its base damage. These functions do not
//* consider armor type in their calculations. If you want armor type damage
//* adjusting, it is recommended to trigger it.
//*
//* Example Usage:
//*     local real basedmg = GetFullDamage(MyUnit, Armor)
//*     local real armodmg = GetReducedDamage(MyUnit, Armor)
//*
//* An objectmerger call is included in this script to automatically generate
//* the bonus life ability if necessary. If you do not modify the 'AIlz' ability
//* in your map, you can replace the LIFE_BONUS_SPELL_ID constant with it and
//* not use the objectmerger call. The
//*
globals
    //Values that should be changed for your map
    private constant real       ARMOR_REDUCTION_MULTIPLIER = 0.06
    private constant integer    LIFE_BONUS_SPELL_ID        = 'lif&'
    private constant attacktype ATTACK_TYPE_USED           = ATTACK_TYPE_CHAOS

    //Values that do not need to be changed
            constant real    ARMOR_INVULNERABLE            = 917451.519
    private constant real    DAMAGE_TEST                   = 16.
    private constant real    DAMAGE_LIFE                   = 30.
    private constant real    NATLOG_094                    =-0.061875
endglobals

////! external ObjectMerger w3a AIlz lif& anam "GetUnitArmorLifeBonus" ansf "" Ilif 1 30 aite 0

function GetUnitArmor takes unit u returns real
    local real    life = GetWidgetLife(u)
    local real    test = life
    local real    redc = 0.
    local boolean enab = false
    local trigger trig = GetTriggeringTrigger()
    if u != null and life >= 0.405 then
        if GetUnitState(u, UNIT_STATE_MAX_LIFE) <= DAMAGE_TEST then
            //Add max life to keep it alive
            call UnitAddAbility(u, LIFE_BONUS_SPELL_ID)
        endif
        if life <= DAMAGE_LIFE then
            //If under the threshold, heal it for the moment
            call SetWidgetLife(u, DAMAGE_LIFE)
            set test = DAMAGE_LIFE
        endif
        static if LIBRARY_IntuitiveDamageSystem then
            //Use the IGNORED type in the IDDS if present so it is 100% ignored
            call UnitDamageTargetEx(u, u, DAMAGE_TEST, ATTACK_TYPE_USED, DAMAGE_TYPE_IGNORED, true)
        else
            if trig != null and IsTriggerEnabled(trig) then
                //Disable the trigger to prevent it registering with damage detection systems
                call DisableTrigger(trig)
                set enab = true
            endif
            call UnitDamageTarget(u, u, DAMAGE_TEST, true, false, ATTACK_TYPE_USED, DAMAGE_TYPE_NORMAL, null)
            if enab then
                //Re-enable the trigger
                call EnableTrigger(trig)
            endif
        endif
        set redc = (DAMAGE_TEST-test+GetWidgetLife(u))/DAMAGE_TEST
        //Remove the max life ability
        call UnitRemoveAbility(u, LIFE_BONUS_SPELL_ID)
        call SetWidgetLife(u, life)
        set trig = null
        if redc >= 1. then
            //Invulnerable
            return ARMOR_INVULNERABLE
        elseif redc < 0. then
            //Negative Armor
            return -Log(redc+1.)/NATLOG_094
        else
            //Positive Armor
            return redc/(ARMOR_REDUCTION_MULTIPLIER*(1.-redc))
        endif
    endif
    set trig = null
    return 0.
endfunction

function GetReducedDamage takes real baseDamage, real armor returns real
    if armor >= 0. then
        return baseDamage*(1.-((armor*ARMOR_REDUCTION_MULTIPLIER)/(1.+ARMOR_REDUCTION_MULTIPLIER*armor)))
    else
        return baseDamage*(2.-Pow(0.94,-armor))
    endif
endfunction

function GetFullDamage takes real damage, real armor returns real
    if armor >= 0. then
        return damage/(1.-((armor*ARMOR_REDUCTION_MULTIPLIER)/(1.+ARMOR_REDUCTION_MULTIPLIER*armor)))
    else
        return damage/(2.-Pow(0.94,-armor))
    endif
endfunction
endlibrary